#include "preHeader.h"
#include "PlayBossMgr.h"
#include "CrossServer.h"
#include "Session/PacketDispatcher.h"

PlayBossMgr::PlayBossMgr()
: m_actvtStatus(ActvtStatus::None)
, m_instSeed(0)
, m_looterMgr(0)
, m_startTime(0)
, m_actvtTime(0)
{
}

PlayBossMgr::~PlayBossMgr()
{
}

WheelTimerMgr *PlayBossMgr::GetWheelTimerMgr()
{
	return &sCrossServer.sWheelTimerMgr;
}

void PlayBossMgr::Init()
{
	auto unpacker = GetGameTimeUnpacker(GameTime::Type::PlayBoss);
	auto&& [triggerPoint, actvtTime] =
		UnpackGameTime4Twinkler(unpacker, ByDailyTrigger);
	CreateTwinklerX(ByDailyTrigger, triggerPoint, actvtTime,
		std::bind(&PlayBossMgr::StartPlay, this, triggerPoint, actvtTime),
		std::bind(&PlayBossMgr::StopPlay, this));
}

GErrorCode PlayBossMgr::HandlePlayerJoin(ObjGUID playerGuid)
{
	if (m_actvtStatus != ActvtStatus::Playing) {
		return ErrActvtNotInTime;
	}

	InstGUID sltInstGuid = InstGUID_NULL;
	auto playerMax = sGameConfig.PlayBossMemberMax;
	for (auto& [instGuid, instStatus] : m_allInstanceStatus) {
		if (instStatus.playerNum < playerMax) {
			sltInstGuid = instGuid;
			break;
		}
	}

	if (sltInstGuid == InstGUID_NULL) {
		sltInstGuid = MakeInstGuid((u16)MapInfo::Type::PlayBoss,
			sGameConfig.PlayBossMapId, ++m_instSeed);
		m_allInstanceStatus.emplace(sltInstGuid, InstanceStatus{});
		NetBuffer buffer;
		buffer << (u64)m_startTime << (u32)m_actvtTime;
		for (auto& [spawnId, bossStatus] : m_allBossStatus) {
			if (bossStatus.totalHPVal == 0 ||
				bossStatus.lossHPVal < bossStatus.totalHPVal) {
				buffer << spawnId
					<< bossStatus.level << bossStatus.lossHPVal;
			}
		}
		sCrossServer.StartInstance(
			sltInstGuid, ObjGUID_NULL, 0, 0, buffer.CastBufferStringView());
	}

	sCrossServer.TeleportPlayer(playerGuid, sltInstGuid, vector3f1f_NULL);

	return CommonSuccess;
}

GErrorCode PlayBossMgr::HandlePlayerChangeTeam(
	ObjGUID playerGuid, const ClientAddr4Cross& clientAddr,
	uint32 teamId, const std::string_view& teamName)
{
	m_looterMgr.ChangeTeam(playerGuid, teamId, teamName);
	SendRank2Client(playerGuid, clientAddr);
	return CommonSuccess;
}

GErrorCode PlayBossMgr::HandlePlayerStrike(
	InstGUID instGuid, const ClientAddr4Cross& clientAddr,
	ObjGUID playerGuid, const std::string_view& playerName,
	uint32 teamId, const std::string_view& teamName,
	uint32 spawnId, uint64 loseHP)
{
	auto itr = m_allBossStatus.find(spawnId);
	if (itr == m_allBossStatus.end()) {
		return CommonNoWarning;
	}

	auto& bossStatus = itr->second;
	if (bossStatus.lossHPVal >= bossStatus.totalHPVal) {
		return CommonNoWarning;
	}

	bossStatus.lossHPVal += loseHP;
	if (playerGuid != ObjGUID_NULL) {
		m_looterMgr.AddScore(
			playerGuid, playerName, teamId, teamName, loseHP);
		SendRank2Client(playerGuid, clientAddr);
	}

	NetPacket trans(G2M_PLAYBOSS_STRIKE);
	trans << instGuid << playerGuid << playerName << spawnId << loseHP;
	SendPacket2AllInstance(trans, instGuid);

	if (bossStatus.lossHPVal >= bossStatus.totalHPVal) {
		NetPacket deadPkt(G2M_PLAYBOSS_DEAD);
		deadPkt << playerGuid << playerName << spawnId;
		SendPacket2AllInstance(deadPkt);
		if (IsAllBossDead()) {
			NetPacket stopPkt(G2M_PLAYBOSS_STOP);
			stopPkt << true;
			SendPacket2AllInstance(stopPkt);
		}
	}

	return CommonSuccess;
}

GErrorCode PlayBossMgr::HandleInstanceSync(
	InstGUID instGuid, PlayBossSyncType syncType, INetPacket& pck)
{
	switch (syncType) {
		case PBST_SyncMember: {
			auto itr = m_allInstanceStatus.find(instGuid);
			if (itr != m_allInstanceStatus.end()) {
				auto& instStatus = itr->second;
				pck >> instStatus.playerNum;
			}
			break;
		}
		case PBST_SyncBoss: {
			while (!pck.IsReadableEmpty()) {
				auto spawnId = pck.Read<uint32>();
				auto hpVal = pck.Read<uint64>();
				auto itr = m_allBossStatus.find(spawnId);
				if (itr != m_allBossStatus.end()) {
					auto& bossStatus = itr->second;
					if (bossStatus.totalHPVal == 0) {
						bossStatus.totalHPVal = hpVal;
					} else {
						DBGASSERT(bossStatus.totalHPVal == hpVal);
					}
				}
			}
			break;
		}
		default: {
			assert(false && "can't reach here.");
			break;
		}
	}
	return CommonSuccess;
}

GErrorCode PlayBossMgr::HandleBroadcast(INetPacket& pck)
{
	while (!pck.IsReadableEmpty()) {
		ObjGUID playerGuid;
		ClientAddr4Cross clientAddr;
		pck >> playerGuid >> clientAddr;
		SendRank2Client(playerGuid, clientAddr);
	}
	return CommonSuccess;
}

void PlayBossMgr::StartPlay(const TriggerPoint& triggerPoint, time_t actvtTime)
{
	m_allInstanceStatus.clear();
	m_allBossStatus.clear();
	REINIT_OBJECT(&m_looterMgr, BOSS_RANK_MAX);

	m_actvtStatus = ActvtStatus::Initing;
	m_startTime = CalcPreviousTriggerPointTime(ByDailyTrigger, triggerPoint);
	m_actvtTime = actvtTime;

	NetPacket rpcReqPck(SMC_QUERY_PLAYER_LEVEL_MAX);
	sPacketDispatcher.RPCInvoke2Mapserver(
		rpcReqPck, [=](INetStream& pck, int32, bool) {
		if (m_actvtStatus == ActvtStatus::Initing) {
			m_actvtStatus = ActvtStatus::Playing;
			BossStatus bossStatus;
			bossStatus.level = pck.Read<uint32>();
			for (auto spawnId : sGameConfig.PlayBossSpawns) {
				m_allBossStatus.emplace(spawnId, bossStatus);
			}
		}
	}, &sCrossServer, DEF_S2S_RPC_TIMEOUT);
}

void PlayBossMgr::StopPlay()
{
	m_actvtStatus = ActvtStatus::None;

	if (!IsAllBossDead()) {
		NetPacket stopPkt(G2M_PLAYBOSS_STOP);
		stopPkt << false;
		SendPacket2AllInstance(stopPkt);
	}
}

void PlayBossMgr::SendRank2Client(
	ObjGUID playerGuid, const ClientAddr4Cross& clientAddr) const
{
	NetPacket pack(SMSG_PLAYBOSS_RANK);
	auto& rankPkt = m_looterMgr.GetRankCachePacket();
	pack.Append(rankPkt.GetBuffer(), rankPkt.GetTotalSize());
	auto&& [rank, totalScore] = m_looterMgr.GetRank(playerGuid);
	pack << (u32)rank << totalScore;
	sCrossServer.TransPacket2Client(clientAddr, pack);
}

void PlayBossMgr::SendPacket2AllInstance(
	const INetPacket& pck, InstGUID exceptInstGuid) const
{
	for (auto& [instGuid, instStatus] : m_allInstanceStatus) {
		if (instGuid != exceptInstGuid) {
			sCrossServer.TransPacket2Instance(instGuid, pck);
		}
	}
}

bool PlayBossMgr::IsAllBossDead() const
{
	for (auto& [spawnId, bossStatus] : m_allBossStatus) {
		if (bossStatus.lossHPVal < bossStatus.totalHPVal) {
			return false;
		}
	}
	return true;
}
